\documentclass[12pt,a4paper,titlepage,twoside]{article}
\input{J_sty_zh.tex}%% 关键
%\usepackage{zhspacing}
\title{smdk2410-led驱动总结}
\author{bingfengxiao}
\date{\today}
%\zhspacing
\begin{document}
\maketitle
\tableofcontents
\clearpage
\section{引言}
	走走停停，终于摸到了Linux驱动的大门口...
	
	第一次写Linux驱动，虽然只是一个不完整的字符设备，还是遇到了一些问题，记录下来，以备以后
	之用.
	
\clearpage
\section{硬件访问}
	在没有操作系统之前，硬件寄存器对程序员来说是可见的，只要有地址，就可以像访问内存一样访问
	这些寄存器。在有了操作系统之后，情况就完全不一样了，Linux对底层寄存器进行了屏蔽，而且，
	由于MMU的存在，我们不能直接使用物理地址对寄存器进行访问。

	由于硬件体系结构的不同，Linux实现了两种外部寄存器访问的方法: I/O端口和I/O内存。关于这两	
	种实现的不同以及对应的体系结构，这里就不再叙述，网络上有很多这方面的介绍。smdk2410的ARM	
	处理器采用I/O内存的方式对寄存器进行访问。

	参考$<$$<$Linux设备驱动程序$>$$>$中的描述，I/O内存仅仅是类似RAM的一个区域，在那里处理器
	可以通过总线访问设备根据计算机平台和所使用的总线不同，I/O内存可能是，也可能不是通过页表
	访问的。如果访问是经由页表进行的，内核必须安排物理地址对设备驱动程序可见(这就意味着在进
	行任何I/O操作之前必须先调用ioremap)。如果访问无需页表，那么I/O内存区域就非常类似于I/O端
	口，可以使用适当形式的函数读写它们。

	不管访问内存时是否需要调用ioremap,都不鼓励直接使用指向I/O内存的指针。硬件的缓存以及编译
	优化会带来一些莫名奇妙的问题。应当使用内核提供的函数对I/O内存进行操作。

	对于I/O内存的访问使用如下一系列函数/宏进行：
	
	\begin{itemize}
		\item I/O内存申请，映射:

\begin{lstlisting}
struct resource *request_mem_region(unsigned long start, unsigned long len,char *name);
void *ioremap(unsigned long phy_addr, unsigned long size);
void *ioremap_nocache(unsigned long phy_addr, unsigned long size);
\end{lstlisting}			

		宏request\_mem\_region用于I/O内存区域的分配；函数ioremap用于物理地址到虚拟地址的
		映射。ioremap\_nochache使对内存的读写操作不使用高速缓存。
		
		\item 释放I/O内存，取消映射:
		
\begin{lstlisting}
void release_mem_region(unsigned long start, unsigned long len);
void iounmap(void *addr);
\end{lstlisting}
		
		\item I/O内存的读取:
		
\begin{lstlisting}
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
\end{lstlisting}

		其addr应该是从ioremap得到的虚拟地址，返回值为从给定I/O内存中读取的值。
		
		\item I/O内存的写入
		
\begin{lstlisting}
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
\end{lstlisting}

		\item I/O内存的连续读/写:
		
\begin{lstlisting}
void ioread8_req(void *addr, void *buf, unsigned long count);
void ioread16_req(void *addr, void *buf, unsigned long count);
void ioread32_req(void *addr, void *buf, unsigned long count);
void iowrite8_req(void *addr, const void *buf, unsigned long count);
void iowrite16_req(void *addr, const void *buf, unsigned long count);
void iowrite32_req(void *addr, const void *buf, unsigned long count);
\end{lstlisting}
		
		上述函数从给定的buf向给定的addr读取或写入count个值， 注意，count以被写入的数据大小
		为单位表示， 比如，ioread32\_req从addr中读取count个32位的数值到buf中。

		\item I/O块操作:
		
\begin{lstlisting}
void memset_io(void *addr, u8 value, unsigned int count);
void memcpy_fromio(void *dest, void *source, unsigned int count);
void memcpy_toio(void *dest, void *source, unsigned int count);
\end{lstlisting}

	\end{itemize}
	
\clearpage
\section{硬件资源互斥}

	直到写这篇文章之前，还在思考如何实现对硬件资源的互斥访问。曾经考虑使用自旋锁及类似机制,
	但这种机制只适合在模块内部使用，模块之间并不好用。因为，一个模块并不知道另外一个模块用
	来保护硬件资源的锁是什么。

	在写"硬件访问"一节时，重新看了一下$<$$<$Linux设备驱动程序$>$$>$中I/O内存一节。仔细研究
	了一下之前一直没弄明白的request\_mem\_region函数。原来，Linux已有对硬件的互斥访问机制。
	Linux中，使用资源要先进行分配。I/O内存也属于资源的一种，使用前也要进行分配，函数
	request\_mem\_region就是用来分配I/O内存的。Linux内核会保证，通过该函数分配的I/O内存会被
	一个申请者使用。该函数有三个参数，start表示要申请的I/O内存起始地址的物理地址，Len表示申
	请的内存大小，name表示分配到的资源名称，可任意填写。

	在弄清楚request\_mem\_region的功能之前，对于I/O内存的访问都是采用对物理地址直接ioremap,
	然后对得到的虚拟地址进行访问，这样，模块间就不可能互斥。为了对request\_mem\_region函数
	的功能进行验证，编写了两上模块，在初始化的时候对同样的I/O内存块进行申请，发现，后面加载
	的模块总是申请不成功。

\clearpage
\section{自动创建设备}
	
	在$<$$<$Linux设备驱动程序$>$$>$中Linux设备模型一节，Linux内核在发现新设备之后，会通知
	udev在/dev/目录中创建相应的节点。但，在成功加载smdk2410\_led的驱动模块之后，在/dev目录
	中并没有相应的节点出现。在/proc/device文件中，可以看到smdk2410\_led分配到的主设备和子
	设备号。根据这两个设备号，使用命令"mknod /dev/smdk2410\_led c \$MAJOR \$MINOR"创建设备
	可以正常对设备进行访问。

	经过Google之后发现，要自动创建/dev下的设备节点需要使用如下两个函数.

\begin{lstlisting}
struct class *class_create(struct module *owner, const char *name);
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata,const char *fmt, ...);
void class_destroy(struct class *cls);
void device_destroy(struct class *cls, dev_t devt);
\end{lstlisting}

	class\_create用于创建一个名称为name的类，创建成功后会在/sys/class目录中显示。

	device\_create用于创新一个新的设备，新建的设备同样会在/sys/device目录或其子目录中显示。
	cls表示该设备隶属的类，parent表示设备的父设备，devt是分配给设备的设备号，drvdata是要传
	给设备驱动的私有数据，可变参数用于设备名称。

	后面两个函数分别用于类与设备的销毁.

	在字符设备注册成功之后调用这两个函数，就可以在/dev目录下创建相应的设备节点。总是感觉创建
	设备与类应该是驱动核心模块的任务，驱动程序没有必要去做这样的事情。字符设备为什么不会自
	己创建设备呢？是因为它没有提供hotplug/uevent方法。
	
\end{document}


